const STATE_IN_NUN_VAR = 'nunvar'
const STATE_IN_NUN_TAG = 'nuntag'
const STATE_IN_NUN_COM = 'nuncom'
const STATE_IN_STRING = 'string'
const STATE_NONE = 'none'

const NUNJUCKS_OPEN_STATES = {
  '{{': STATE_IN_NUN_VAR,
  '{%': STATE_IN_NUN_TAG,
  '{#': STATE_IN_NUN_COM
}

const NUNJUCKS_CLOSE_STATES = {
  '}}': STATE_IN_NUN_VAR,
  '%}': STATE_IN_NUN_TAG,
  '#}': STATE_IN_NUN_COM
}

/**
 * Format a JSON string without parsing it as JavaScript.
 *
 * Code taken from jsonlint (http://zaa.ch/jsonlint/)
 *
 * @param json
 * @param indentChars
 * @param replaceUnicode
 * @returns {string}
 */
// module.exports.prettify = function (json, indentChars = '\t', replaceUnicode = true) {
export default {
  prettify,
  minify
}

function minify (json, replaceUnicode = true) {
  if (!json) {
    return ''
  }
  if (replaceUnicode) {
    try {
      json = _convertUnicode(json)
    } catch (err) {
      // Just in case (should never happen)
      console.warn('Prettify failed to handle unicode', err)
    }
  }
  var object = JSON.parse(json)
  var minified = JSON.stringify(object, null, 0)
  return minified
}

function prettify (json, indentChars = '\t', replaceUnicode = true) {
  if (!json) {
    return ''
  }

  // Convert the unicode. To correctly mimic JSON.stringify(JSON.parse(json), null, indentChars)
  // we need to convert all escaped unicode characters to proper unicode characters.
  if (replaceUnicode) {
    try {
      json = _convertUnicode(json)
    } catch (err) {
      // Just in case (should never happen)
      console.warn('Prettify failed to handle unicode', err)
    }
  }

  let i = 0
  // const il = json.length
  const tab = indentChars
  let newJson = ''
  let indentLevel = 0
  let currentChar = null
  let nextChar = null
  let nextTwo = null
  let state = STATE_NONE
  // for (i < il; i += 1;) {
  // for (var i=0;i++;i<=5) {

  for (const il = json.length; i < il; i += 1) {
    currentChar = json.charAt(i)
    nextChar = json.charAt(i + 1) || ''
    nextTwo = currentChar + nextChar

    if (state === STATE_IN_STRING) {
      if (currentChar === '"') {
        state = STATE_NONE
        newJson += currentChar
        continue
      } else if (currentChar === '\\') {
        newJson += currentChar + nextChar
        i++
        continue
      } else {
        newJson += currentChar
        continue
      }
    }

    // Close Nunjucks states
    if (Object.values(NUNJUCKS_CLOSE_STATES).includes(state)) {
      const closeState = NUNJUCKS_CLOSE_STATES[nextTwo]
      if (closeState) {
        state = STATE_NONE
        if (closeState === STATE_IN_NUN_COM) {
          // Put comments on their own lines
          newJson += nextTwo + '\n' + _repeatString(tab, indentLevel)
        } else {
          newJson += nextTwo
        }
        i++
        continue
      } else {
        newJson += currentChar
        continue
      }
    }

    // ~~~~~~~~~~~~~~~~~~~~~~ //
    // Handle "nothing" State //
    // ~~~~~~~~~~~~~~~~~~~~~~ //

    // Open Nunjucks states
    const nextState = NUNJUCKS_OPEN_STATES[nextTwo]
    if (nextState) {
      state = nextState
      newJson += nextTwo
      i++
      continue
    }

    switch (currentChar) {
      case ',':
        newJson += currentChar + '\n' + _repeatString(tab, indentLevel)
        continue
      case '{':
        if (nextChar === '}') {
          newJson += currentChar + nextChar
          i++
        } else {
          indentLevel++
          newJson += currentChar + '\n' + _repeatString(tab, indentLevel)
        }
        continue
      case '[':
        if (nextChar === ']') {
          newJson += currentChar + nextChar
          i++
        } else {
          indentLevel++
          newJson += currentChar + '\n' + _repeatString(tab, indentLevel)
        }
        continue
      case '}':
        indentLevel--
        newJson += '\n' + _repeatString(tab, indentLevel) + currentChar
        continue
      case ']':
        indentLevel--
        newJson += '\n' + _repeatString(tab, indentLevel) + currentChar
        continue
      case ':':
        newJson += ': '
        continue
      case '"':
        state = STATE_IN_STRING
        newJson += currentChar
        continue
      case ' ':
      case '\n':
      case '\t':
      case '\r':
        // Don't add whitespace
        continue
      default:
        newJson += currentChar
        continue
    }
  }

  // Remove lines that only contain whitespace
  return newJson.replace(/^\s*\n/gm, '')
}

function _repeatString (s, count) {
  return new Array(count + 1).join(s)
}

/**
 * Convert escaped unicode characters to real characters. Any JSON parser will do this by
 * default. This is really fast too. Around 25ms for ~2MB of data with LOTS of unicode.
 *
 * @param originalStr
 * @returns {string}
 * @private
 */
function _convertUnicode (originalStr) {
  let m
  let c
  let cStr
  let lastI = 0

  // Matches \u#### but not \\u####
  const unicodeRegex = /\\u[0-9a-fA-F]{4}/g

  let convertedStr = ''
  while ((m = unicodeRegex.exec(originalStr))) {
    // Don't convert if the backslash itself is escaped
    if (originalStr[m.index - 1] === '\\') {
      continue
    }

    try {
      cStr = m[0].slice(2) // Trim off start
      c = String.fromCharCode(parseInt(cStr, 16))
      if (c === '"') {
        // Escape it if it's double quotes
        c = `\\${c}`
      }

      // + 1 to account for first matched (non-backslash) character
      convertedStr += originalStr.slice(lastI, m.index) + c
      lastI = m.index + m[0].length
    } catch (err) {
      // Some reason we couldn't convert a char. Should never actually happen
      console.warn('Failed to convert unicode char', m[0], err)
    }
  }

  // Finally, add the rest of the string to the end.
  convertedStr += originalStr.slice(lastI, originalStr.length)

  return convertedStr
}
